﻿namespace FlotDotNet
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Reflection;

    /// <summary>
    /// Provides a collection for attributes
    /// </summary>
    public class FlotAttributeCollection : Dictionary<string, string>
    {
        private List<string> Attributes { get; set; }
        private FlotElementBase Owner { get; set; }
        private Type OwnerType { get; set; }

        /// <summary>
        /// Creates a new instance of the FlotAttributeSerializer class
        /// </summary>
        internal FlotAttributeCollection(FlotElementBase owner)
        {
            Attributes = new List<string>();
            Owner = owner;
            OwnerType = Owner.GetType();
        }
        /// <summary>
        /// Populates the collection using reflecction
        /// </summary>
        internal void Populate()
        {
            // Look through all the properties with the FlotPropertyAttribute
            // TODO: Is there a way to get just public properties
            PropertyInfo[] pp = OwnerType.GetProperties();
            foreach (PropertyInfo p in OwnerType.GetProperties())
            {
                object[] attributes = p.GetCustomAttributes(true);
                foreach (object a in attributes)
                {
                    FlotPropertyAttribute faa = a as FlotPropertyAttribute;
                    if (faa != null)
                    {
                        // load the property to get the value from the owner
                        object value = p.GetValue(Owner, null);
                        
                        // Build the name
                        // If this is a nested property we dont include the name
                        // If a name has been set on the property this is used
                        // Otherwise we use the name of the property camelCased
                        //string name = faa.NestedProperty ? string.Empty : faa.Name ?? p.Name[0].ToString().ToLower() + p.Name.Substring(1);
                        string name = null;
                        if (!string.IsNullOrEmpty(faa.Name) && !faa.NestedProperty )
                        {
                            name = faa.Name;
                        }
                        else if(faa.NestedProperty)
                        {
                            name = null;
                        }
                        else
                        {
                            name = p.Name[0].ToString().ToLower() + p.Name.Substring(1);
                        }

                        //Add(name, value == null ? null : Convert.ChangeType(value, t), faa.SerializationOptions);
                        Add(name, value, faa.SerializationOptions);
                    }
                }
            }

            FieldInfo[] fi = OwnerType.GetFields();
            foreach (FieldInfo f in fi)
            {
                object[] attributes = f.GetCustomAttributes(true);
                foreach (object a in attributes)
                {
                    FlotPropertyAttribute faa = a as FlotPropertyAttribute;
                    if (faa != null)
                    {
                        object value = f.GetValue(Owner);
                        string name = null;
                        if (!string.IsNullOrEmpty(faa.Name))
                        {
                            name = faa.Name;
                        }
                        else
                        {
                            name = f.Name[0].ToString().ToLower() + f.Name.Substring(1);
                        }
                        Add(name, value, faa.SerializationOptions);
                    }
                }
            }
        }

        /// <summary>
        /// Adds a string attribute to the collection
        /// </summary>
        /// <param name="value">The value of the attribute</param>
        public void Add(string value)
        {
            Add(string.Empty, value, FlotSerializationOptions.None);
        }
        /// <summary>
        /// Adds an object attribute to the collection
        /// </summary>
        /// <param name="name">The name of the attribute</param>
        /// <param name="value">The value of the attribute</param>
        public void Add(string name, object value)
        {
            Add(name, value, FlotSerializationOptions.None);
        }
        /// <summary>
        /// Adds an object attribute to the collection
        /// </summary>
        /// <param name="name">The name of the attribute</param>
        /// <param name="value">The value of the attribute</param>
        /// <param name="options">Serialization options when serializing the attribute</param>
        public void Add(string name, object value, FlotSerializationOptions options)
        {
            // we check if the value is a bool
            // as we have special serilazation options
            if (value is bool?)
            {
                Add(name, (bool?)value);
                return;
            }

            // IFlotElement defines special serialization options
            if (value is IFlotElement)
            {
                Add(name, (IFlotElement)value);
                return;
            }

            // default add the parameter as a string
            Add(name, value == null ? string.Empty : value.ToString(), options);
        }
        /// <summary>
        /// Adds a string attribute to the collection
        /// </summary>
        /// <param name="name">The name of the attribute</param>
        /// <param name="value">The value of the attribute</param>
        public new void Add(string name, string value)
        {
            Add(name, value, FlotSerializationOptions.None);
        }
        /// <summary>
        /// Adds a nullable value type attribute to the collection
        /// </summary>
        /// <param name="name">The name of the attribute</param>
        /// <param name="value">The value of the attribute</param>
        public void Add<T>(string name, Nullable<T> value) where T : struct
        {
            // default bool to be lower case
            if (value is bool?)
            {
                Add(name, value, FlotSerializationOptions.LowerCase);
            }
            else
            {
                Add(name, value, FlotSerializationOptions.None);
            }
        }
        /// <summary>
        /// Adds a nullable value type attribute to the collection
        /// </summary>
        /// <param name="name">The name of the attribute</param>
        /// <param name="value">The value of the attribute</param>
        /// <param name="options">Serialization options when serializing the attribute</param>
        public void Add<T>(string name, Nullable<T> value, FlotSerializationOptions options) where T : struct
        {
            Add(name, value.HasValue ? value.ToString() : string.Empty, options);
        }
        /// <summary>
        /// Adds an attribute to the collection
        /// </summary>
        /// <param name="name">The name of the attribute</param>
        /// <param name="value">The value of the attribute</param>
        public void Add(string name, IFlotElement value)
        {
            // we only add if the object has a value
            if (value != null)
            {
                Add(name, value.Serialize());
            }
        }
        /// <summary>
        /// Adds a string attribute to the collection
        /// </summary>
        /// <param name="name">The name of the attribute</param>
        /// <param name="value">The value of the attribute</param>
        /// <param name="options">The serialization options when serializing the attribute</param>
        public void Add(string name, string value, FlotSerializationOptions options)
        {
            // if we are not allowing empty values
            // return if the value is empty
            if ((options & FlotSerializationOptions.AllowEmptyValue) != FlotSerializationOptions.AllowEmptyValue && string.IsNullOrEmpty(value))
                return;

            // to ensure no null reference exceptions
            if (value == null)
                value = string.Empty;

            // lower case if required
            if ((options & FlotSerializationOptions.LowerCase) == FlotSerializationOptions.LowerCase)
                value = value.ToLower();

            // quote if required
            if ((options & FlotSerializationOptions.QuoteValue) == FlotSerializationOptions.QuoteValue)
                value = "'" + value.Replace("'", "\\'") + "'";

            // the value is serialized including the name if set
            // e.g. AttributeName: AttributeValue
            // or AttributeValue
            Attributes.Add(name + (string.IsNullOrEmpty(name) ? string.Empty : ": ") + value);
        }
        /// <summary>
        /// Serializes all the attributes currently in the collection
        /// </summary>
        /// <returns>A string which contains the serialized attributes</returns>
        internal string Serialize()
        {
            return Serialize(false);
        }
        /// <summary>
        /// Serializes all the attributes in the collection
        /// </summary>
        /// <param name="populate">True will populate the attributes from the owner type</param>
        /// <returns>A string which contains the serialized attributes</returns>
        internal string Serialize(bool populate)
        {
            // populate the collection if required
            if (populate)
                Populate();

            // if we have anything to serialize
            // comma separate the values and return
            if (Attributes.Count > 0)
            {
                string serialized = string.Join(", ", Attributes.ToArray());

                // we can still end up with nothing to return
                if (string.IsNullOrEmpty(serialized))
                    return string.Empty;

                // append the name if required
                if (!string.IsNullOrEmpty(Owner.ElementName))
                {
                    serialized = Owner.ElementName + ": { " + serialized + " }";
                    ////serialized = "{ " + serialized + " }";
                }
                else if (!serialized.StartsWith("{"))
                {
                    serialized = "{ " + serialized + " }";
                }

                return serialized;
            }

            // nothing to return
            return string.Empty;
        }
    }
}